package com.ruoyi.ai.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ai.api.bo.WxMaAddBo;
import com.ruoyi.ai.api.bo.WxMaConsumeBo;
import com.ruoyi.ai.domain.WxNumChange;
import com.ruoyi.ai.domain.bo.WxBackChangeNumBo;
import com.ruoyi.ai.domain.bo.WxNumChangeBo;
import com.ruoyi.ai.domain.bo.WxUserBo;
import com.ruoyi.ai.domain.vo.WxNumChangeVo;
import com.ruoyi.ai.domain.vo.WxUserVo;
import com.ruoyi.ai.enums.ChangeBizTypeEnum;
import com.ruoyi.ai.enums.ChangeTypeEnum;
import com.ruoyi.ai.mapper.WxNumChangeMapper;
import com.ruoyi.ai.service.IWxNumChangeService;
import com.ruoyi.ai.service.IWxUserService;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.UserService;
import com.ruoyi.common.enums.UserType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.system.domain.vo.RegisterGiveNumConf;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.ruoyi.common.constant.UserConstants.MEMBER_STATUS_FALSE;

/**
 * 次数变更流水Service业务层处理
 *
 * @author yh
 * @date 2024-04-11
 */
@RequiredArgsConstructor
@Service
public class WxNumChangeServiceImpl implements IWxNumChangeService {

    private final WxNumChangeMapper baseMapper;

    private final IWxUserService wxUserService;

    private final ISysConfigService configService;

    private final ISysUserService sysUserService;
    private final UserService userService;

    /**
     * 查询次数变更流水
     */
    @Override
    public WxNumChangeVo queryById(Long changeId) {
        return baseMapper.selectVoById(changeId);
    }

    /**
     * 查询次数变更流水列表
     */
    @Override
    public TableDataInfo<WxNumChangeVo> queryPageList(WxNumChangeBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<WxNumChange> lqw = buildQueryWrapper(bo);
        Page<WxNumChangeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询次数变更流水列表
     */
    @Override
    public List<WxNumChangeVo> queryList(WxNumChangeBo bo) {
        LambdaQueryWrapper<WxNumChange> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<WxNumChange> buildQueryWrapper(WxNumChangeBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<WxNumChange> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getWxUserId() != null, WxNumChange::getWxUserId, bo.getWxUserId());
        lqw.eq(bo.getChangeType() != null, WxNumChange::getChangeType, bo.getChangeType());
        lqw.eq(bo.getBizType() != null, WxNumChange::getBizType, bo.getBizType());
        lqw.eq(StringUtils.isNotBlank(bo.getUserType()), WxNumChange::getUserType, bo.getUserType());
        return lqw;
    }

    /**
     * 新增次数变更流水
     */
    @Override
    public Boolean insertByBo(WxNumChangeBo bo) {
        WxNumChange add = BeanUtil.toBean(bo, WxNumChange.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setChangeId(add.getChangeId());
        }
        return flag;
    }

    /**
     * 修改次数变更流水
     */
    @Override
    public Boolean updateByBo(WxNumChangeBo bo) {
        WxNumChange update = BeanUtil.toBean(bo, WxNumChange.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(WxNumChange entity) {
        entity.setDelFlag(UserConstants.DEL_FLAG_FALSE);
        //TODO 做一些数据校验,如唯一约束
        if (entity.getChangeId() != null) {
            throw new ServiceException("记录不允许修改");
        }
        //处理下更新时间
        if (entity.getChangeTime() == null) {
            entity.setChangeTime(new Date());
        }
        //处理下变更的参数
        //判断是否可以扣减，并且保存记录
        WxUserVo wxUserVo = null;
        SysUser sysUser = null;
        Integer chatNum = 0;

        if (entity.getUserType() == null || entity.getUserType().equals(UserType.WX_CUSTOMER.getUserType())) {
            wxUserVo = wxUserService.queryById(entity.getWxUserId());
            if (wxUserVo == null) {
                throw new ServiceException("用户不存在，请检查:" + entity.getWxUserId());
            }
            chatNum = wxUserVo.getChatNum();
        } else {
            sysUser = sysUserService.selectUserById(entity.getWxUserId());
            if (sysUser == null) {
                throw new ServiceException("web用户不存在，请检查:" + entity.getWxUserId());
            }
            // 这里需要根据系统用户获取相应的次数
            chatNum = sysUser.getChatNum();
        }

        //检测数量
        Integer bizType = entity.getBizType();
        Integer changeNum = entity.getChangeNum();
        Integer changeType = entity.getChangeType();
        if (changeNum == 0) {
            throw new ServiceException("变更数量不能为0");
        }
        ChangeTypeEnum changeTypeEnum = ChangeTypeEnum.getByType(changeType);
        if (changeTypeEnum == null) {
            throw new ServiceException("业务类型不存在");
        }
        if (Objects.equals(changeTypeEnum, ChangeTypeEnum.USER_CONSUME)
            || Objects.equals(changeTypeEnum, ChangeTypeEnum.ADMIN_REDUCE)) {
            if (changeNum > 0) {
                throw new ServiceException("扣减数量必须小于0");
            }
        } else {
            if (changeNum < 0) {
                throw new ServiceException("增加数量必须大于0");
            }
        }
        ChangeBizTypeEnum bizTypeEnum = ChangeBizTypeEnum.getByType(bizType);
        Integer remainingNum = this.checkAndGetRemainingNum(bizTypeEnum, chatNum, changeNum);
        entity.setRemainingNum(remainingNum);

        // 根据不同用户类型更新不同的用户表
        if (entity.getUserType() == null || entity.getUserType().equals(UserType.WX_CUSTOMER.getUserType())) {
            WxUserBo updateUser = BeanUtil.toBean(wxUserVo, WxUserBo.class);
            switch (bizTypeEnum) {
                case AI_CHAT:
                    updateUser.setChatNum(remainingNum);
                    break;
            }
            if (updateUser.getChatNum() == 0 &&
                updateUser.getOcrNum() == 0 &&
                updateUser.getVoiceNum() == 0) {
                updateUser.setMemberStatus(MEMBER_STATUS_FALSE);
                updateUser.setMemberGradeId(null);
            }
            wxUserService.updateNumWithNull(updateUser);
        } else {
            // 针对系统用户的更新逻辑
            // 可能需要调用 sysUserService 的相关方法更新系统用户的使用次数
            // 20250913修复更新数量后用户角色关联关系消失问题
            if (bizTypeEnum == ChangeBizTypeEnum.AI_CHAT) {
                sysUserService.updateUserChatNum(entity.getWxUserId(),remainingNum);
            }
        }
    }

    private Integer checkAndGetRemainingNum(ChangeBizTypeEnum bizTypeEnum, Integer chatNum, Integer consumeNum) {
        Integer remainingNum;
        switch (bizTypeEnum) {
            case AI_CHAT:
                remainingNum = chatNum + consumeNum;
                break;
            default:
                throw new ServiceException("不支持的业务类型");
        }
        if (remainingNum < 0) {
            throw new ServiceException("您的剩余次数不足，请先购买");
        }
        return remainingNum;
    }

    /**
     * 批量删除次数变更流水
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Override
    public Boolean consume(WxMaConsumeBo maBo) {
        WxNumChangeBo wxNumChangeBo = new WxNumChangeBo();
        wxNumChangeBo.setBizType(maBo.getBizType());
        wxNumChangeBo.setBizId(maBo.getBizId());
        wxNumChangeBo.setWxUserId(maBo.getWxUserId());
        wxNumChangeBo.setUserType(maBo.getUserType());
        if (maBo.getWxUserId() == null) {
            wxNumChangeBo.setWxUserId(LoginHelper.getUserId());
        }
        wxNumChangeBo.setChangeType(ChangeTypeEnum.USER_CONSUME.getType());
        wxNumChangeBo.setChangeNum(-maBo.getConsumeNum());
        insertByBo(wxNumChangeBo);
        return true;
    }

    @Override
    public Boolean registerGiveNum(WxUserBo wxUserBo) {
        RegisterGiveNumConf registerGiveNumConf = configService.selectGiveNumConf();
        wxUserBo.setChatNum(registerGiveNumConf.getChatNum() != null ? registerGiveNumConf.getChatNum() : 50);
//        wxUserBo.setOcrNum(registerGiveNumConf.getOcrNum() != null ? registerGiveNumConf.getOcrNum() : 10);
//        wxUserBo.setVoiceNum(registerGiveNumConf.getVoiceNum() != null ? registerGiveNumConf.getVoiceNum() : 10);
        saveNum(wxUserBo.getWxUserId(), ChangeBizTypeEnum.AI_CHAT.getType(), ChangeTypeEnum.REGISTER_GIVE.getType(), wxUserBo.getChatNum(), wxUserBo.getWxUserId(), null);
        return true;
    }

    @Override
    public Boolean openVipSaveNum(WxUserBo wxUserBo, Integer chatNum, Integer ocrNum, Integer voiceNum, Long orderId) {
        saveNum(wxUserBo.getWxUserId(), ChangeBizTypeEnum.AI_CHAT.getType(), ChangeTypeEnum.USER_ADD.getType(), chatNum, orderId, null);
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean changeNumFromBack(WxBackChangeNumBo wxBackChangeNumBo) {
        Integer changeType = wxBackChangeNumBo.getChangeNum() > 0
            ? ChangeTypeEnum.ADMIN_ADD.getType()
            : ChangeTypeEnum.ADMIN_REDUCE.getType();
        saveNum(wxBackChangeNumBo.getWxUserId(), wxBackChangeNumBo.getBizType(), changeType, wxBackChangeNumBo.getChangeNum(), null, wxBackChangeNumBo.getRemark());
        return true;
    }

    @Override
    public Boolean addNum(WxMaAddBo wxMaAddBo) {
        Long wxUserId = LoginHelper.getUserId();
        Integer bizType = ChangeBizTypeEnum.AI_CHAT.getType();
        Integer changeType = wxMaAddBo.getChangeType();
        Integer changeNum = wxMaAddBo.getChangeNum();
        String userIp = wxMaAddBo.getUserIp();
        String deviceInfo = wxMaAddBo.getDeviceInfo();

        if (Objects.equals(changeType, ChangeTypeEnum.AD_GIVE.getType())) {
            // 防刷机制：检查频率限制
            checkAdRewardRateLimit(wxUserId, userIp, deviceInfo);
            changeNum = 15;
        } else {
            throw new ServiceException("暂不支持的类型");
        }
        saveNum(wxUserId, bizType, changeType, changeNum, null, "IP:" + userIp + ",设备:" + deviceInfo);
        return true;
    }

    /**
     * 检查广告奖励频率限制
     * @param wxUserId 用户ID
     * @param userIp 用户IP
     * @param deviceInfo 设备信息
     */
    private void checkAdRewardRateLimit(Long wxUserId, String userIp, String deviceInfo) {
        // 用户ID+日期作为key，限制单用户每日获取次数
        String userDayKey = "ad_reward:user:" + wxUserId + ":" + DateUtil.format(new Date(), "yyyyMMdd");
        String userHourKey = "ad_reward:user:" + wxUserId + ":" + DateUtil.format(new Date(), "yyyyMMddHH");
        // IP限制，防止同一网络下多账号刷量
        String ipDayKey = "ad_reward:ip:" + userIp + ":" + DateUtil.format(new Date(), "yyyyMMdd");
        // 设备限制，防止同一设备多账号刷量
        String deviceDayKey = "ad_reward:device:" + deviceInfo + ":" + DateUtil.format(new Date(), "yyyyMMdd");

        // 从Redis获取当前计数
        Integer userDayCount = RedisUtils.getCacheObject(userDayKey);
        Integer userHourCount = RedisUtils.getCacheObject(userHourKey);
        Integer ipDayCount = RedisUtils.getCacheObject(ipDayKey);
        Integer deviceDayCount = RedisUtils.getCacheObject(deviceDayKey);

        // 设置默认值
        userDayCount = userDayCount == null ? 0 : userDayCount;
        userHourCount = userHourCount == null ? 0 : userHourCount;
        ipDayCount = ipDayCount == null ? 0 : ipDayCount;
        deviceDayCount = deviceDayCount == null ? 0 : deviceDayCount;

        // 检查限制：每用户每天最多30次广告奖励
        if (userDayCount >= 30) {
            throw new ServiceException("您今日的广告奖励次数已达上限，明天再来吧");
        }

        // 检查限制：每用户每小时最多10次广告奖励
        if (userHourCount >= 10) {
            throw new ServiceException("您的观看太频繁了，请稍后再试");
        }

        // 检查IP限制：每IP每天最多50次广告奖励
        if (ipDayCount >= 50) {
            throw new ServiceException("当前网络观看次数已达上限");
        }

        // 检查设备限制：每设备每天最多50次广告奖励
        if (deviceDayCount >= 50) {
            throw new ServiceException("当前设备观看次数已达上限");
        }

        // 增加计数并设置过期时间
        RedisUtils.setCacheObject(userDayKey, userDayCount + 1, Duration.ofDays(1L));
        RedisUtils.setCacheObject(userHourKey, userHourCount + 1, Duration.ofHours(1L));
        RedisUtils.setCacheObject(ipDayKey, ipDayCount + 1, Duration.ofDays(1L));
        RedisUtils.setCacheObject(deviceDayKey, deviceDayCount + 1, Duration.ofDays(1L));
    }

    public void saveNum(Long wxUserId, Integer bizType, Integer changeType, Integer changeNum, Long bizId, String remark) {
        WxNumChangeBo wxNumChangeBo = new WxNumChangeBo();
        wxNumChangeBo.setBizType(bizType);
        wxNumChangeBo.setBizId(bizId);
        wxNumChangeBo.setWxUserId(wxUserId);
        wxNumChangeBo.setChangeType(changeType);
        wxNumChangeBo.setChangeNum(changeNum);
        wxNumChangeBo.setRemark(remark);
        if (wxNumChangeBo.getUserType() == null) {
            wxNumChangeBo.setUserType(UserType.WX_CUSTOMER.getUserType());
        }
        insertByBo(wxNumChangeBo);
    }
}
